#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <sys/stat.h>

#include "Observer.h"


#define die(str, args...) do { \
        perror(str); \
        exit(EXIT_FAILURE); \
    } while(0)

#define UINPUT_DEVICE1 "/dev/uinput"
#define UINPUT_DEVICE2 "/dev/input/uinput"


class MouseObserver: public IObserver
{
private:
	int fd;
	struct uinput_user_dev uidev;
	struct input_event ev;
	int dx, dy;

public:
	MouseObserver()
	{
		//fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
		fd = open_uinput();
		if (fd <= 0)
			die("error: open");

		ioctl(fd, UI_SET_EVBIT, EV_REL);
		ioctl(fd, UI_SET_EVBIT, EV_KEY);
		ioctl(fd, UI_SET_RELBIT, REL_X);
		ioctl(fd, UI_SET_RELBIT, REL_Y);
		ioctl(fd, UI_SET_RELBIT, REL_WHEEL);
		ioctl(fd, UI_SET_KEYBIT, BTN_MOUSE);
		ioctl(fd, UI_SET_KEYBIT, KEY_LEFTSHIFT);
		ioctl(fd, UI_SET_KEYBIT, KEY_LEFTCTRL);
		ioctl(fd, UI_SET_KEYBIT, KEY_PLAY);
		ioctl(fd, UI_SET_KEYBIT, KEY_PLAYPAUSE);
		ioctl(fd, UI_SET_KEYBIT, KEY_STOP);
	    ioctl(fd, UI_SET_KEYBIT, KEY_Q);

		memset(&uidev, 0, sizeof(uidev)); // Intialize the uInput device to NULL

		strncpy(uidev.name, "virtual mouse", UINPUT_MAX_NAME_SIZE);
		write(fd, &uidev, sizeof(uidev));

		if (ioctl(fd, UI_DEV_CREATE))
			die("Unable to create UINPUT device.");
	}

	int open_uinput() {
	  struct stat st;
	  if (stat(UINPUT_DEVICE1, &st) == 0 && S_ISCHR(st.st_mode)) {
	    return open(UINPUT_DEVICE1, O_WRONLY);
	  }
	  if (stat(UINPUT_DEVICE2, &st) == 0 && S_ISCHR(st.st_mode)) {
	    return open(UINPUT_DEVICE2, O_WRONLY);
	  }
	  return -1;
	}

	//see: /usr/include/linux/input.h
	void handleEvent(int _cursor, int _dx, int _dy)
	{
		//mouseMove(_dx, _dy);
		//mouseWheel(_dy);
		//send_key_event(KEY_LEFTSHIFT, 1);
		send_key_event(KEY_PLAYPAUSE, 1);
		send_key_event(KEY_PLAYPAUSE, 0);
		//send_key_event(KEY_LEFTSHIFT, 0);
		send_syn();
	}

	void mouseMove(int _dx, int _dy)
	{
		if (fd <= 0) return;

		ev.type = EV_REL;
		ev.code = REL_X;
		ev.value = dx = _dx;
		write(fd, &ev, sizeof(ev));

		ev.type = EV_REL;
		ev.code = REL_Y;
		ev.value = dy = _dy;
		write(fd, &ev, sizeof(ev));

		send_syn();
	}

	void mouseWheel(int _delta)
	{
		if (fd <= 0) return;

		gettimeofday(&ev.time, NULL);
		ev.type = EV_REL;
		ev.code = REL_WHEEL;
		ev.value = _delta;
		write(fd, &ev, sizeof(ev));

		send_syn();
	}

	void send_key_event(int button, int value)
	{
	  if (fd <= 0) return;

	  gettimeofday(&ev.time, NULL);
	  ev.type = EV_KEY;
	  ev.code = button;
	  ev.value = value;
	  write(fd, &ev, sizeof(ev));

	}

	void send_syn()
	{
	   if (fd <= 0) return;

	   ev.type = EV_SYN;
	   ev.code = SYN_REPORT;
	   ev.value = 0;
	   write(fd, &ev, sizeof(ev));
	}

	~MouseObserver()
	{
		ioctl(fd, UI_DEV_DESTROY);
		close(fd);
	}

};
